#include "network/fd.hpp"

#include "base/assert.hpp"
#include "base/log.hpp"

#include <errno.h>
#include <fcntl.h>
#include <utility>

namespace ibox::network {

Fd::Fd(int fd) : m_detail{std::make_shared<Detail>()} { m_detail->fd = fd; }

Fd::Fd(int fd, const CloseFunc &close_func)
    : m_detail{std::make_shared<Detail>()} {
    m_detail->fd = fd;
    m_detail->close_func = close_func;
}

Fd::~Fd() {
    if (m_detail == nullptr) {
        return;
    }

    expect(m_detail->ref_count > 0);

    --m_detail->ref_count;
    if (m_detail->ref_count == 0) {
        if (m_detail->fd >= 0) {
            if (m_detail->close_func) {
                m_detail->close_func(m_detail->fd);
            } else {
                ::close(m_detail->fd);
            }
        }
    }
}

Fd::Fd(const Fd &other) {
    if (other.m_detail != nullptr) {
        ++other.m_detail->ref_count;
        m_detail = other.m_detail;
    }
}

Fd &Fd::operator=(const Fd &other) {
    if (this != &other) {
        reset();
        if (other.m_detail != nullptr) {
            ++other.m_detail->ref_count;
            m_detail = other.m_detail;
        }
    }
    return *this;
}

void Fd::swap(Fd &other) { std::swap(m_detail, other.m_detail); }

Fd::Fd(Fd &&other) noexcept { swap(other); }
Fd &Fd::operator=(Fd &&other) noexcept {
    if (this != &other) {
        reset();
        swap(other);
    }
    return *this;
}
void Fd::reset() {
    Fd tmp;
    swap(tmp);
}

void Fd::close() {
    if (m_detail != nullptr && m_detail->fd >= 0) {
        if (m_detail->close_func) {
            m_detail->close_func(m_detail->fd);
            m_detail->close_func = nullptr;
        } else {
            ::close(m_detail->fd);
        }
        m_detail->fd = -1;
    }
}

ssize_t Fd::read(void *ptr, size_t size) const {
    if (m_detail == nullptr) {
        return -1;
    }

    return ::read(m_detail->fd, ptr, size);
}

ssize_t Fd::readv(const struct iovec *iov, int iovcnt) const {
    if (m_detail == nullptr) {
        return -1;
    }

    return ::readv(m_detail->fd, iov, iovcnt);
}

ssize_t Fd::write(const void *ptr, size_t size) const {
    if (m_detail == nullptr) {
        return -1;
    }

    return ::write(m_detail->fd, ptr, size);
}

ssize_t Fd::writev(const struct iovec *iov, int iovcnt) const {
    if (m_detail == nullptr) {
        return -1;
    }

    return ::writev(m_detail->fd, iov, iovcnt);
}

void Fd::set_nonblock(bool enable) const {
    if (m_detail == nullptr) {
        return;
    }

#ifdef O_NONBLOCK
    int old_flags = fcntl(m_detail->fd, F_GETFL, 0);
    int new_flags = old_flags;

    if (enable) {
        new_flags |= O_NONBLOCK;
    } else {
        new_flags &= ~O_NONBLOCK;
    }

    if (new_flags != old_flags) {
        int ret = fcntl(m_detail->fd, F_SETFL, new_flags);
        if (ret == -1) {
            LOG_DEBUG("fcntl error, errno: {}", errno);
        }
    }
#else
#    error No way found to set non-blocking mode for fds.
#endif
}

bool Fd::is_nonblock() const {
    if (m_detail == nullptr) {
        return false;
    }

#ifdef O_NONBLOCK
    int flags = fcntl(m_detail->fd, F_GETFL, 0);
    return (flags & O_NONBLOCK) != 0;
#else
#    error No way found to set non-blocking mode for fds.
#endif
}

void Fd::set_close_on_exec() const {
    if (m_detail == nullptr) {
        return;
    }

#ifdef FD_CLOEXEC
    int old_flags = fcntl(m_detail->fd, F_GETFD, 0);
    int new_flags = old_flags | FD_CLOEXEC;
    if (new_flags != old_flags) {
        int ret = fcntl(m_detail->fd, F_SETFL, new_flags);
        if (ret == -1) {
            LOG_DEBUG("fcntl error, errno: {}", errno);
        }
    }
#else
    UNUSED(fd);
#endif
}

Fd Fd::open(const char *filename, int flags) {
    int fd = ::open(filename, flags);
    if (fd < 0) {
        LOG_ERROR(
            "open file:{} flags:{} fail, errno:{}", filename, flags, errno);
        return Fd();
    }

    return Fd(fd);
}

} // namespace ibox::network
